#include "rendMesh.hpp"
#include "rendMeshUtils.hpp"
#include "rendMaterial.hpp"

#include <memory.h>
#include <float.h>
#include <iostream>
#include <fstream>
#include <algorithm>

#include "lib3ds.h"

namespace rend
{

const AttrElementType MeshIndexed::attr_element_index =
	sizeof(Index) == 2 ? ATTR_ELEMENT_UINT16 : ATTR_ELEMENT_UINT32;

const std::string MeshIndexed::attr_semantics_position("Vertex");
const std::string MeshIndexed::attr_semantics_normal("Normal");
const std::string MeshIndexed::attr_semantics_blend_weights("Weight");
const std::string MeshIndexed::attr_semantics_texcoord("TexCoord");
const std::string MeshIndexed::attr_semantics_index("index");


void MeshIndexed::draw()
{
	const Material::Program* prog = Material::prog;

	if (!prog)
	{
		fprintf(stderr, "%s: no gpu program to draw with; bailing out\n", __FUNCTION__);
		return;
	}

	GLint attr_locations[32];
	unsigned num_active_attr = 0;

	for (std::vector< std::string >::const_iterator it = m_vtx.begin(); it != m_vtx.end(); ++it)
	{
		const std::string& name = *it;
		const AttrBuffer16* attr_buffer;

		if (name.empty() || !(attr_buffer = m_reg_buf->seek(name)) ||
			attr_buffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_VERTEX || !attr_buffer->actuate(name))
		{
			continue;
		}

		const unsigned num_attr = attr_buffer->getAttrCount();

		for (unsigned ai = 0; ai < num_attr; ++ai)
		{
			const AttrDescriptor& desc = attr_buffer->getAttrDescriptor(ai);
			const unsigned offset = attr_buffer->getAttrOffset(ai);

			GLenum type;
			GLint attrLoc = -1;

			if (desc.semantics == attr_semantics_position)
			{
				if (desc.element_count != 2 &&
					desc.element_count != 3 &&
					desc.element_count != 4)
				{
					fprintf(stderr, "error: invalid semantics' element count in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}

				// cover all valid element types (short, int, float, double)
				switch (desc.element_type)
				{
				case ATTR_ELEMENT_INT16:
					type = GL_SHORT;
					break;
				case ATTR_ELEMENT_INT32:
					type = GL_INT;
					break;
				case ATTR_ELEMENT_FLOAT32:
					type = GL_FLOAT;
					break;
				case ATTR_ELEMENT_FLOAT64:
					type = GL_DOUBLE;
					break;
				default:
					fprintf(stderr, "error: invalid semantics' element type in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}
			}
			else if (desc.semantics == attr_semantics_normal)
			{
				if (desc.element_count != 3)
				{
					fprintf(stderr, "error: invalid semantics' element count in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}

				// cover all vaild element types (byte, short, int, float, double)
				switch (desc.element_type)
				{
				case ATTR_ELEMENT_INT8:
					type = GL_BYTE;
					break;
				case ATTR_ELEMENT_INT16:
					type = GL_SHORT;
					break;
				case ATTR_ELEMENT_INT32:
					type = GL_INT;
					break;
				case ATTR_ELEMENT_FLOAT32:
					type = GL_FLOAT;
					break;
				case ATTR_ELEMENT_FLOAT64:
					type = GL_DOUBLE;
					break;
				default:
					fprintf(stderr, "error: invalid semantics' element type in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}
			}
			else if (desc.semantics == attr_semantics_blend_weights)
			{
				if (desc.element_count != 4)
				{
					fprintf(stderr, "error: invalid semantics' element count in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}

				// cover all vaild element types (float)
				switch (desc.element_type)
				{
				case ATTR_ELEMENT_FLOAT32:
					type = GL_FLOAT;
					break;
				default:
					fprintf(stderr, "error: invalid semantics' element type in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}
			}
			else if (desc.semantics == attr_semantics_texcoord)
			{
				if (desc.element_count != 1 &&
					desc.element_count != 2 &&
					desc.element_count != 3 &&
					desc.element_count != 4)
				{
					fprintf(stderr, "error: invalid semantics' element count in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}

				// cover all valid element types (byte, short, int, float, double)
				switch (desc.element_type)
				{
				case ATTR_ELEMENT_INT8:
					type = GL_BYTE;
					break;
				case ATTR_ELEMENT_INT16:
					type = GL_SHORT;
					break;
				case ATTR_ELEMENT_INT32:
					type = GL_INT;
					break;
				case ATTR_ELEMENT_FLOAT32:
					type = GL_FLOAT;
					break;
				case ATTR_ELEMENT_FLOAT64:
					type = GL_DOUBLE;
					break;
				default:
					fprintf(stderr, "error: invalid semantics' element type in attribute array buffer '%s', semantics '%s'\n",
						name.c_str(), desc.semantics.c_str());
					continue;
				}
			}
			// more semantics to follow

			attrLoc = prog->getAttribLocation(desc.semantics);

			if (-1 != attrLoc)
			{
				glVertexAttribPointerARB(attrLoc, desc.element_count, type, GL_FALSE, attr_buffer->getBufferRecSize(), (const GLvoid *) offset);
				glEnableVertexAttribArrayARB(attrLoc);

				attr_locations[num_active_attr++] = attrLoc;
				assert(num_active_attr < sizeof(attr_locations) / sizeof(attr_locations[0]));
			}

			if (reportGLError(stderr))
				fprintf(stderr, "error: failed to set up vertex attribute array from buffer '%s', semantics '%s'\n",
					name.c_str(), desc.semantics.c_str());
		}
	}

	const AttrBuffer16* attr_buffer;

	if (m_tri.length() && (attr_buffer = m_reg_buf->seek(m_tri)) &&
		attr_buffer->getBufferType() == AttrBuffer16::BUFFER_TYPE_INDEX && attr_buffer->actuate(m_tri))
	{
		assert(attr_buffer->getAttrCount() == 1);

		const AttrDescriptor& desc = attr_buffer->getAttrDescriptor(0);

		GLenum type = GL_NONE;

		// cover all valid element types (unsigned byte, unsigned short, unsigned int)
		switch (desc.element_type)
		{
		case ATTR_ELEMENT_UINT8:
			type = GL_UNSIGNED_BYTE;
			break;
		case ATTR_ELEMENT_UINT16:
			type = GL_UNSIGNED_SHORT;
			break;
		case ATTR_ELEMENT_UINT32:
			type = GL_UNSIGNED_INT;
			break;
		}

		if (GL_NONE != type)
		{
			glDrawElements(GL_TRIANGLES, attr_buffer->getBufferLength() * desc.element_count, type, NULL);

			if (reportGLError(stderr))
				fprintf(stderr, "error: failed to draw elements with attribute array buffer '%s', semantics '%s'\n",
					m_tri.c_str(), desc.semantics.c_str());
		}
		else
			fprintf(stderr, "error: invalid semantics element type in attribute array buffer '%s', semantics '%s'\n",
				m_tri.c_str(), desc.semantics.c_str());
	}
	else
		fprintf(stderr, "error: missing or invalid index buffer at draw call, the one specified is '%s'\n", m_tri.c_str());

	for (unsigned i = 0; i < num_active_attr; ++i)
		glDisableVertexAttribArrayARB(attr_locations[i]);
}


void MeshIndexed::drawWire()
{
	glPolygonMode(GL_FRONT, GL_LINE);

	draw();

	glPolygonMode(GL_FRONT, GL_FILL);
}


MeshIndexed::MeshIndexed()
: m_reg_buf(&AttrBuffer16::s_default_register)
{
}


MeshIndexed::MeshIndexed(const MeshIndexed& src)
: Visual(src)
, m_reg_buf(src.m_reg_buf)
, m_vtx(src.m_vtx)
, m_tri(src.m_tri)
{
}


MeshIndexed::~MeshIndexed()
{
}


bool MeshIndexed::load(
	FILE* f,
	const std::string& registration_name)
{
	assert(f);

	unsigned nv = 0;

	if (1 != fscanf(f, "%u", &nv))
		return false;

	if (nv > 1 << sizeof(Index) * 8)
		return false;

	VectorArray< float, 3 > v(nv);
	VectorArray< float, 3 > n(nv);

	if (!v.count() || !n.count())
		return false;

	for (unsigned i = 0; i < nv; ++i)
	{
		float (&vi)[3] = v.get(i);
		float (&ni)[3] = n.get(i);

		if (6 != fscanf(f, "%f %f %f %f %f %f",
					&vi[0], &vi[1], &vi[2],
					&ni[0], &ni[1], &ni[2]))
		{
			return false;
		}
	}

	unsigned nt = 0;

	if (1 != fscanf(f, "%u", &nt))
		return false;

	VectorArray< Index, 3 > tri(nt);

	if (!tri.count())
		return false;

	for (unsigned i = 0; i < nt; ++i)
	{
		int trip[3];

		if (3 != fscanf(f, "%d %d %d",
					&trip[0],
					&trip[1],
					&trip[2]))
		{
			return false;
		}

		Index (&ti)[3] = tri.get(i);

		ti[0] = trip[0];
		ti[1] = trip[1];
		ti[2] = trip[2];
	}

	// done with the file; filter out unused vertices and remap indices accordingly
	Index *remap = reinterpret_cast< Index* >(malloc(sizeof(Index) * nv));
	assert(remap);

	memset(remap, -1, nv * sizeof(*remap));

	unsigned used_count = 0;

	for (unsigned i = 0; i < tri.count(); ++i)
	{
		Index (&ti)[3] = tri.get(i);

		if (remap[ti[0]])
		{
			remap[ti[0]] = 0;
			++used_count;
		}
		if (remap[ti[1]])
		{
			remap[ti[1]] = 0;
			++used_count;
		}
		if (remap[ti[2]])
		{
			remap[ti[2]] = 0;
			++used_count;
		}
	}

	unsigned skip_count = 0;

	mBBox.setMinimum(vect3(  FLT_MAX,  FLT_MAX,  FLT_MAX  ));
	mBBox.setMaximum(vect3( -FLT_MAX, -FLT_MAX, -FLT_MAX  ));

	for (unsigned i = 0; i < nv; ++i)
	{
		if (!remap[i])
		{
			vect3::cast(v.get(i - skip_count)) = vect3::cast(v.get(i));
			vect3::cast(n.get(i - skip_count)) = vect3::cast(n.get(i));

			mBBox.merge(v.get(i));
		}
		else
			++skip_count;

		remap[i] = skip_count;
	}

	assert(used_count + skip_count == nv);

	for (unsigned i = 0; i < tri.count(); ++i)
	{
		Index (&ti)[3] = tri.get(i);

		ti[0] -= remap[ti[0]];
		ti[1] -= remap[ti[1]];
		ti[2] -= remap[ti[2]];
	}

	free(remap);

	printf("triangles: %d, vertices: %d, indexed: %d\n", tri.count(), nv, used_count);

	static unsigned mesh_count;
	char str[2][128];

	const char* mesh_name = registration_name.length()
		? registration_name.c_str()
		: "+anonymous";

	sprintf(str[0], "%s_%03d_vertex", mesh_name, mesh_count);
	sprintf(str[1], "%s_%03d_index", mesh_name, mesh_count);
	++mesh_count;

	const std::string vb_name(str[0]);

	AttrBuffer16* vb = m_reg_buf->insert(vb_name);

	vb->setBufferType(AttrBuffer16::BUFFER_TYPE_VERTEX);
	vb->setBufferLength(used_count);

	static const AttrDescriptor scr[] =
	{
		AttrDescriptor(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3),
		AttrDescriptor(attr_semantics_normal, ATTR_ELEMENT_FLOAT32, 3)
	};

	vb->setAttrScript(sizeof(scr) / sizeof(scr[0]), scr);	

	AttrBuffer16::AttrAccessor< float, 3 > vfinal;
	AttrBuffer16::AttrAccessor< float, 3 > nfinal;

	vb->acquireLock();

	vb->getAttrAccessor(scr[0], vfinal);
	vb->getAttrAccessor(scr[1], nfinal);

	for (unsigned i = 0; i < used_count; ++i)
	{
		vect3::cast(vfinal.get(i)) = vect3::cast(v.get(i));
		vect3::cast(nfinal.get(i)).normalise(vect3::cast(n.get(i)));
	}

	vb->releaseLock();

	const std::string ib_name(str[1]);

	AttrBuffer16* ib = m_reg_buf->insert(ib_name);

	ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);

	static const AttrDescriptor scri[] =
	{
		AttrDescriptor(attr_semantics_index, attr_element_index, 3)
	};

	ib->setAttrScript(sizeof(scri) / sizeof(scri[0]), scri);
	ib->setBulkTransfer(tri);

	setNumVertBuffers(1);
	setVertexBuffer(vb_name, 0);
	setIndexBuffer(ib_name);

	return true;
}


bool MeshIndexed::save(FILE* f) const
{
	assert(f);

	AttrBuffer16::AttrAccessorImm< float, 3 > v;
	AttrBuffer16::AttrAccessorImm< float, 3 > n;
	AttrBuffer16::AttrAccessorImm< Index, 3 > tri;

	static const AttrDescriptor desc_v(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3);
	static const AttrDescriptor desc_n(attr_semantics_normal, ATTR_ELEMENT_FLOAT32, 3);
	static const AttrDescriptor desc_i(attr_semantics_index, attr_element_index, 3);

	const unsigned nbuf = getNumVertBuffers();

	const AttrBuffer16* vbuffer = NULL;
	const AttrBuffer16* nbuffer = NULL;

	for (unsigned bi = 0; bi < nbuf; ++bi)
	{
		const std::string& name = getVertexBuffer(bi);

		const AttrBuffer16* buffer = m_reg_buf->seek(name);

		if (buffer == NULL || buffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_VERTEX)
			continue;

		if (vbuffer == NULL && -1U != buffer->findAttr(desc_v))
			vbuffer = buffer;

		if (nbuffer == NULL && -1U != buffer->findAttr(desc_n))
			nbuffer = buffer;
	}

	if (vbuffer == NULL || nbuffer == NULL)
		return false;

	const std::string& name = getIndexBuffer();

	const AttrBuffer16* ibuffer = m_reg_buf->seek(name);

	if (ibuffer == NULL || ibuffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_INDEX)
		return false;

	vbuffer->acquireLock();
	vbuffer->getAttrAccessor(desc_v, v);

	nbuffer->acquireLock();
	nbuffer->getAttrAccessor(desc_n, n);

	ibuffer->acquireLock();
	ibuffer->getAttrAccessor(desc_i, tri);

	if (0 == v.count() ||
		0 == n.count() ||
		0 == tri.count())
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	assert(v.count() == n.count());

	if (0 > fprintf(f, "%d\n", v.count()))
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	for (unsigned i = 0; i < v.count(); ++i)
	{
		const float (&vi)[3] = v.get(i);
		const float (&ni)[3] = n.get(i);

		if (0 > fprintf(f, "%f %f %f %f %f %f\n",
					vi[0], vi[1], vi[2],
					ni[0], ni[1], ni[2]))
		{
			vbuffer->releaseLock();
			nbuffer->releaseLock();
			ibuffer->releaseLock();

			return false;
		}
	}

	if (0 > fprintf(f, "%d\n", tri.count()))
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	for (unsigned i = 0; i < tri.count(); ++i)
	{
		const Index (&ti)[3] = tri.get(i);

		if (0 > fprintf(f, "%d %d %d\n",
					ti[0], ti[1], ti[2]))
		{
			vbuffer->releaseLock();
			nbuffer->releaseLock();
			ibuffer->releaseLock();

			return false;
		}
	}

	vbuffer->releaseLock();
	nbuffer->releaseLock();
	ibuffer->releaseLock();

	return true;
}


bool MeshIndexed::save_texcoord(
	FILE* f) const
{
	assert(f);

	AttrBuffer16::AttrAccessorImm< float, 3 > v;
	AttrBuffer16::AttrAccessorImm< float, 3 > n;
	AttrBuffer16::AttrAccessorImm< float, 2 > c;
	AttrBuffer16::AttrAccessorImm< Index, 3 > tri;

	static const AttrDescriptor desc_v(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3);
	static const AttrDescriptor desc_n(attr_semantics_normal, ATTR_ELEMENT_FLOAT32, 3);
	static const AttrDescriptor desc_c(attr_semantics_texcoord, ATTR_ELEMENT_FLOAT32, 2);
	static const AttrDescriptor desc_i(attr_semantics_index, attr_element_index, 3);

	const unsigned nbuf = getNumVertBuffers();

	const AttrBuffer16* vbuffer = NULL;
	const AttrBuffer16* nbuffer = NULL;
	const AttrBuffer16* cbuffer = NULL;

	for (unsigned bi = 0; bi < nbuf; ++bi)
	{
		const std::string& name = getVertexBuffer(bi);

		const AttrBuffer16* buffer = m_reg_buf->seek(name);

		if (buffer == NULL || buffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_VERTEX)
			continue;

		if (vbuffer == NULL && -1U != buffer->findAttr(desc_v))
			vbuffer = buffer;

		if (nbuffer == NULL && -1U != buffer->findAttr(desc_n))
			nbuffer = buffer;

		if (cbuffer == NULL && -1U != buffer->findAttr(desc_c))
			cbuffer = buffer;
	}

	if (vbuffer == NULL || nbuffer == NULL || cbuffer == NULL)
		return false;

	const std::string& name = getIndexBuffer();

	const AttrBuffer16* ibuffer = m_reg_buf->seek(name);

	if (ibuffer == NULL || ibuffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_INDEX)
		return false;

	vbuffer->acquireLock();
	vbuffer->getAttrAccessor(desc_v, v);

	nbuffer->acquireLock();
	nbuffer->getAttrAccessor(desc_n, n);

	cbuffer->acquireLock();
	cbuffer->getAttrAccessor(desc_c, c);

	ibuffer->acquireLock();
	ibuffer->getAttrAccessor(desc_i, tri);

	if (0 == v.count() ||
		0 == n.count() ||
		0 == c.count() ||
		0 == tri.count())
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		cbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	assert(v.count() == n.count());

	if (0 > fprintf(f, "%d\n", v.count()))
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		cbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	for (unsigned i = 0; i < v.count(); ++i)
	{
		const float (&vi)[3] = v.get(i);
		const float (&ni)[3] = n.get(i);
		const float (&ci)[2] = c.get(i);

		if (0 > fprintf(f, "%f %f %f %f %f %f %f %f\n",
					vi[0], vi[1], vi[2],
					ni[0], ni[1], ni[2],
					ci[0], ci[1]))
		{
			vbuffer->releaseLock();
			nbuffer->releaseLock();
			cbuffer->releaseLock();
			ibuffer->releaseLock();

			return false;
		}
	}

	if (0 > fprintf(f, "%d\n", tri.count()))
	{
		vbuffer->releaseLock();
		nbuffer->releaseLock();
		cbuffer->releaseLock();
		ibuffer->releaseLock();

		return false;
	}

	for (unsigned i = 0; i < tri.count(); ++i)
	{
		const Index (&ti)[3] = tri.get(i);

		if (0 > fprintf(f, "%d %d %d\n",
					ti[0], ti[1], ti[2]))
		{
			vbuffer->releaseLock();
			nbuffer->releaseLock();
			cbuffer->releaseLock();
			ibuffer->releaseLock();

			return false;
		}
	}

	vbuffer->releaseLock();
	nbuffer->releaseLock();
	cbuffer->releaseLock();
	ibuffer->releaseLock();

	return true;
}


bool MeshIndexed::load(
	const std::string& filename,
	std::vector< MeshIndexed >& meshes,
	AttrBuffer16::Register* reg_buf)
{
	assert(reg_buf);

	FILE* f = fopen(filename.c_str(), "rb");

	if (!f)
	{
		printf("error: failed to open mesh file '%s' for reading\n", filename.c_str());
		return false;
	}

	const std::vector< MeshIndexed >::iterator former_end = meshes.end();

	while (true)
	{
		MeshIndexed mesh;
		mesh.setRegister(reg_buf);

		if (!mesh.load(f, filename))
			break;

		meshes.push_back(mesh);
	}

	fclose(f);

	return former_end != meshes.end();
}


bool MeshIndexed::save(
	const std::string& filename,
	const std::vector< MeshIndexed >& meshes)
{
	FILE* f = fopen(filename.c_str(), "wb");

	if (!f)
	{
		printf("error: failed to open mesh file '%s' for writing\n", filename.c_str());
		return false;
	}

	std::vector< MeshIndexed >::const_iterator it = meshes.begin();
	for (; it != meshes.end(); ++it)
	{
		if (!it->save(f))
			break;
	}

	fclose(f);

	return it == meshes.end();
}


bool MeshIndexed::save_texcoord(
	const std::string& filename,
	const std::vector< MeshIndexed >& meshes)
{
	FILE* f = fopen(filename.c_str(), "wb");

	if (!f)
	{
		printf("error: failed to open mesh file '%s' for writing\n", filename.c_str());
		return false;
	}

	std::vector< MeshIndexed >::const_iterator it = meshes.begin();
	for (; it != meshes.end(); ++it)
	{
		if (!it->save_texcoord(f))
			break;
	}

	fclose(f);

	return it == meshes.end();
}


void MeshIndexed::upload() const
{
	const AttrBuffer16* attr_buffer;

	for (std::vector< std::string >::const_iterator it = m_vtx.begin(); it != m_vtx.end(); ++it)
		if (it->length() && (attr_buffer = m_reg_buf->seek(*it)) && attr_buffer->getBufferType() == AttrBuffer16::BUFFER_TYPE_VERTEX)
			if (!attr_buffer->actuate(*it))
				fprintf(stderr, "error: failed to upload vertex buffer '%s' to server\n", it->c_str());

	if (m_tri.length() && (attr_buffer = m_reg_buf->seek(m_tri)) && attr_buffer->getBufferType() == AttrBuffer16::BUFFER_TYPE_INDEX)
		if (!attr_buffer->actuate(m_tri))
			fprintf(stderr, "error: failed to upload index buffer '%s' to server\n", m_tri.c_str());
}


static inline unsigned
seek_prior_vertex(
	const unsigned i,
	const float (* const n)[3],
	const Lib3dsFace* const faces)
{
	for (unsigned j = 0; j < i; ++j)
	{
		vect3 dn = vect3::cast(n[i]);
		dn.sub(vect3::cast(n[j]));
#if 1
		const float epsilon = 1e-11;
#else
		const float epsilon = FLT_EPSILON;
#endif
		if (fabsf(dn[0]) < epsilon &&
			fabsf(dn[1]) < epsilon &&
			fabsf(dn[2]) < epsilon &&
			faces[i / 3].index[i % 3] == faces[j / 3].index[j % 3])
		{
			return j;
		}
	}

	return -1U;
}


bool MeshIndexed::load3ds_smooth(
	const std::string& filename,
	std::vector< MeshIndexed >& meshes,
	AttrBuffer16::Register* reg_buf)
{
	assert(reg_buf);

	Lib3dsFile* f = lib3ds_file_open(filename.c_str());

	if (!f)
	{
		printf("error: failed to open 3ds file '%s' for reading\n", filename.c_str());
		return false;
	}

	const std::vector< MeshIndexed >::iterator former_end = meshes.end();

	lib3ds_file_eval(f, 0);

	unsigned short node_id = 0;

	while (true)
	{
		Lib3dsNode* node = lib3ds_file_node_by_id(f, node_id++);
		
		if (!node)
			break;

		Lib3dsMesh* mesh3ds = lib3ds_file_mesh_for_node(f, node);

		if (!mesh3ds)
			continue;

		matx4 mesh_matx;
		mesh_matx.identity();

		if (node->type == LIB3DS_NODE_MESH_INSTANCE)
		{
			Lib3dsMeshInstanceNode* mi_node = reinterpret_cast< Lib3dsMeshInstanceNode* >(node);

			vect3 node_pivot = vect3::cast(mi_node->pivot);

			mesh_matx.transpose(matx4::cast(mesh3ds->matrix));

			matx4 inv;
			inv.invert(mesh_matx);

			const vect3& sub_x = vect3::castU(mesh_matx[0]);
			const vect3& sub_y = vect3::castU(mesh_matx[1]);
			const vect3& sub_z = vect3::castU(mesh_matx[2]);

			vect3 pos;
			mesh_matx.getColS(3, pos);

			pos.sub(vect3(
				sub_x.dp(node_pivot),
				sub_y.dp(node_pivot),
				sub_z.dp(node_pivot)));

			mesh_matx.setColS(3, pos);

			mesh_matx.mur(inv);
		}

		// presume the mesh has disjoint topology, ie. N(vertices) = N(indices)
		const unsigned num_possible_verts = 3 * mesh3ds->nfaces;

		VectorArray< float, 3 > v(num_possible_verts);
		VectorArray< float, 3 > n(num_possible_verts);
		VectorArray< float, 2 > s(num_possible_verts);
		VectorArray< Index, 3 > faces(mesh3ds->nfaces);

		const unsigned sans_texcos = 0 == mesh3ds->texcos ? 1 : 0;

		float (*m)[3] = (float(*)[3]) malloc(sizeof(float[3]) * num_possible_verts);
		assert(m);

		lib3ds_mesh_calculate_vertex_normals(mesh3ds, m);

		Index idx_unique = Index(-1);

		Index* idx = (Index*) malloc(sizeof(Index) * num_possible_verts);
		assert(idx);

		bool overflow = false;

		for (unsigned i = 0; i < mesh3ds->nfaces && !overflow; ++i)
		{
			for (unsigned j = 0; j < 3; ++j)
			{
				const unsigned prior = seek_prior_vertex(i * 3 + j, m, mesh3ds->faces);

				if (-1U == prior)
				{
					if (0 == ++idx_unique && (i || j))
					{
						overflow = true;
						break;
					}

					vect3::cast(v.get(idx_unique)) = vect3::cast(mesh3ds->vertices[mesh3ds->faces[i].index[j]]);
					vect3::cast(n.get(idx_unique)) = vect3::cast(m[i * 3 + j]);

					if (!sans_texcos)
						vect2::cast(s.get(idx_unique)) = vect2::cast(mesh3ds->texcos[mesh3ds->faces[i].index[j]]);

					faces.get(i)[j] = idx_unique;
				}
				else
					faces.get(i)[j] = idx[prior];

				idx[i * 3 + j] = idx_unique;
			}
		}

		free(idx);
		free(m);

		if (overflow)
		{
			fprintf(stderr, "error: expanded vertex count exceeds mesh indexing limit; mesh discarded\n");
			continue;
		}

		const unsigned num_unique_verts = unsigned(idx_unique) + 1;
		printf("triangles: %d, vertices: %d, indexed: %d\n", mesh3ds->nfaces, mesh3ds->nvertices, num_unique_verts);
		
		MeshIndexed mesh;
		mesh.setRegister(reg_buf);

		static unsigned mesh_count;
		char str[2][16];

		sprintf(str[0], "_%03d_vertex", mesh_count);
		sprintf(str[1], "_%03d_index", mesh_count);
		++mesh_count;

		const std::string vb_name = filename + str[0];

		AttrBuffer16* vb = reg_buf->insert(vb_name);

		vb->setBufferType(AttrBuffer16::BUFFER_TYPE_VERTEX);
		vb->setBufferLength(num_unique_verts);

		static const AttrDescriptor scr[] =
		{
			AttrDescriptor(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3),
			AttrDescriptor(attr_semantics_normal, ATTR_ELEMENT_FLOAT32, 3),
			AttrDescriptor(attr_semantics_texcoord, ATTR_ELEMENT_FLOAT32, 2)
		};

		vb->setAttrScript(sizeof(scr) / sizeof(scr[0]) - sans_texcos, scr);

		AttrBuffer16::AttrAccessor< float, 3 > vfinal;
		AttrBuffer16::AttrAccessor< float, 3 > nfinal;
		AttrBuffer16::AttrAccessor< float, 2 > sfinal;

		vb->acquireLock();

		vb->getAttrAccessor(scr[0], vfinal);
		vb->getAttrAccessor(scr[1], nfinal);
		vb->getAttrAccessor(scr[2], sfinal);

		for (unsigned i = 0; i < num_unique_verts; ++i)
		{
#if 1
			mesh_matx.transform3(vect3::cast(v.get(i)), vect3::cast(vfinal.get(i)));
#else
			vect3::cast(vfinal.get(i)) = vect3::cast(v.get(i));
#endif
			vect3::cast(nfinal.get(i)) = vect3::cast(n.get(i));

			if (sans_texcos)
				continue;

			vect2::cast(sfinal.get(i)) = vect2::cast(s.get(i));
		}

		vb->releaseLock();

		const std::string ib_name = filename + str[1];

		AttrBuffer16* ib = reg_buf->insert(ib_name);

		ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);

		static const AttrDescriptor scri[] =
		{
			AttrDescriptor(attr_semantics_index, attr_element_index, 3)
		};

		ib->setAttrScript(sizeof(scri) / sizeof(scri[0]), scri);
		ib->setBulkTransfer(faces);

		mesh.setNumVertBuffers(1);
		mesh.setVertexBuffer(vb_name, 0);
		mesh.setIndexBuffer(ib_name);

		float bmin[3], bmax[3];
		lib3ds_mesh_bounding_box(mesh3ds, bmin, bmax);

		vect3 bmin_corr, bmax_corr;
		mesh_matx.transform3(vect3::cast(bmin), bmin_corr);
		mesh_matx.transform3(vect3::cast(bmax), bmax_corr);

		mesh.setBBox(Box3(bmin_corr, bmax_corr));

		meshes.push_back(mesh);
	}

	lib3ds_file_free(f);

	return former_end != meshes.end();
}


bool MeshIndexed::load3ds_nosmooth(
	const std::string& filename,
	std::vector< MeshIndexed >& meshes,
	AttrBuffer16::Register* reg_buf)
{
	assert(reg_buf);

	Lib3dsFile* f = lib3ds_file_open(filename.c_str());

	if (!f)
	{
		printf("error: failed to open 3ds file '%s' for reading\n", filename.c_str());
		return false;
	}

	const std::vector< MeshIndexed >::iterator former_end = meshes.end();

	lib3ds_file_eval(f, 0);

	unsigned short node_id = 0;

	while (true)
	{
		Lib3dsNode *node = lib3ds_file_node_by_id(f, node_id++);
		
		if (!node)
			break;

		Lib3dsMesh *mesh3ds = lib3ds_file_mesh_for_node(f, node);

		if (!mesh3ds)
			continue;

		VectorArray< Index, 3 > faces(mesh3ds->nfaces);

		for (unsigned i = 0; i < mesh3ds->nfaces; ++i)
		{
			const Lib3dsFace *face3ds = mesh3ds->faces + i;

			faces.get(i)[0] = face3ds->index[0];
			faces.get(i)[1] = face3ds->index[1];
			faces.get(i)[2] = face3ds->index[2];
		}

		printf("triangles: %d, vertices: %d\n", mesh3ds->nfaces, mesh3ds->nvertices);

		const unsigned num_unique_verts = mesh3ds->nvertices;
		const unsigned sans_texcos = 0 == mesh3ds->texcos ? 1 : 0;

		MeshIndexed mesh;
		mesh.setRegister(reg_buf);

		static unsigned mesh_count;
		char str[2][16];

		sprintf(str[0], "_%03d_vertex", mesh_count);
		sprintf(str[1], "_%03d_index", mesh_count);
		++mesh_count;

		const std::string vb_name = filename + str[0];

		AttrBuffer16* vb = reg_buf->insert(vb_name);

		vb->setBufferType(AttrBuffer16::BUFFER_TYPE_VERTEX);
		vb->setBufferLength(num_unique_verts);

		static const AttrDescriptor scr[] =
		{
			AttrDescriptor(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3),
			AttrDescriptor(attr_semantics_normal, ATTR_ELEMENT_FLOAT32, 3),
			AttrDescriptor(attr_semantics_texcoord, ATTR_ELEMENT_FLOAT32, 2)
		};

		vb->setAttrScript(sizeof(scr) / sizeof(scr[0]) - sans_texcos, scr);

		AttrBuffer16::AttrAccessor< float, 3 > vfinal;
		AttrBuffer16::AttrAccessor< float, 3 > nfinal;
		AttrBuffer16::AttrAccessor< float, 2 > sfinal;

		vb->acquireLock();

		vb->getAttrAccessor(scr[0], vfinal);
		vb->getAttrAccessor(scr[1], nfinal);
		vb->getAttrAccessor(scr[2], sfinal);

		for (unsigned i = 0; i < num_unique_verts; ++i)
		{
			vect3::cast(vfinal.get(i)) = vect3::cast(mesh3ds->vertices[i]);

			if (sans_texcos)
				continue;

			vect2::cast(sfinal.get(i)) = vect2::cast(mesh3ds->texcos[i]);
		}

		completeFrom< 16, Index, 3 >(nfinal, vfinal, faces);

		vb->releaseLock();

		const std::string ib_name = filename + str[1];

		AttrBuffer16* ib = reg_buf->insert(ib_name);

		ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);

		static const AttrDescriptor scri[] =
		{
			AttrDescriptor(attr_semantics_index, attr_element_index, 3)
		};

		ib->setAttrScript(sizeof(scri) / sizeof(scri[0]), scri);
		ib->setBulkTransfer(faces);

		mesh.setNumVertBuffers(1);
		mesh.setVertexBuffer(vb_name, 0);
		mesh.setIndexBuffer(ib_name);

		float bmin[3], bmax[3];
		lib3ds_mesh_bounding_box(mesh3ds, bmin, bmax);

		mesh.setBBox(Box3(bmin, bmax));

		meshes.push_back(mesh);
	}

	lib3ds_file_free(f);

	return former_end != meshes.end();
}


bool MeshIndexed::loadAge(
	const std::string& filename,
	std::vector< MeshIndexed >& meshes,
	AttrBuffer16::Register* reg_buf)
{
	assert(reg_buf);

	std::ifstream source;
	source.open(filename.c_str(), std::ios::binary);

	if (!source.good())
		return false;

	// header
	uint32 magic(0);
	uint32 version(0);
	source.read(reinterpret_cast< char* >(&magic), sizeof(magic));
	source.read(reinterpret_cast< char* >(&version), sizeof(version));

	swapEndiannessIfHostBig(magic);
	swapEndiannessIfHostBig(version);

	fprintf(stdout, "mesh magic, version: %.4s, %u\n", (char*) &magic, version);

	static const uint32 sMagic('hsem');
	static const uint32 sVersion(100);

	if (magic != sMagic || version != sVersion)
	{
		fprintf(stderr, "error: mesh of unknown magic/version; bailing out\n");
		return false;
	}

	// mesh info
	uint8 softwareSkinning(0);
	uint16 primitiveType(0);
	source.read(reinterpret_cast< char* >(&softwareSkinning), sizeof(softwareSkinning));
	source.read(reinterpret_cast< char* >(&primitiveType), sizeof(primitiveType));

	swapEndiannessIfHostBig(primitiveType);

	if (softwareSkinning)
	{
		fprintf(stderr, "error: mesh uses software skinning; bailing out\n");
		return false;
	}

	enum PrimitiveType
	{
		PT_TRIANGLE_LIST	= 4,
	};

	enum AttribType
	{
		AT_FLOAT1			= 0,
		AT_FLOAT2			= 1,
		AT_FLOAT3			= 2,
		AT_FLOAT4			= 3,
		AT_UBYTE4			= 9,
		AT_COLOR			= 11
	};

	enum AttribSemantic
	{
		AS_POSITION			= 1,
		AS_BLEND_WEIGHTS	= 2,
		AS_NORMAL			= 4,
		AS_COLOR			= 5,
		AS_TEXTURE_COORD	= 7,
		AS_BITANGENT		= 8,
		AS_TANGENT			= 9	
	};

	enum IndexType
	{
		INDEX_16BIT			= 0,
		INDEX_32BIT			= 1
	};

	const PrimitiveType type = (PrimitiveType) primitiveType;
	assert(type == PT_TRIANGLE_LIST);

	// vertex declaration
	uint16 num_attr;
	source.read(reinterpret_cast<char*>(&num_attr), sizeof(num_attr));

	swapEndiannessIfHostBig(num_attr);

	fprintf(stdout, "vertex attributes: %d\n", num_attr);

	const unsigned max_num_buffers = 8;
	std::vector< AttrDescriptor > bind_attr[max_num_buffers];

	for (unsigned i = 0; i < num_attr; ++i)
	{ 
		uint16 src_buffer;
		uint16 type;
		uint16 semantic;
		uint16 offset;
		uint16 index;
		source.read(reinterpret_cast< char* >(&src_buffer), sizeof(src_buffer));
		source.read(reinterpret_cast< char* >(&type), sizeof(type));
		source.read(reinterpret_cast< char* >(&semantic), sizeof(semantic));
		source.read(reinterpret_cast< char* >(&offset), sizeof(offset));
		source.read(reinterpret_cast< char* >(&index), sizeof(index));

		swapEndiannessIfHostBig(src_buffer);
		swapEndiannessIfHostBig(type);
		swapEndiannessIfHostBig(semantic);
		swapEndiannessIfHostBig(offset);
		swapEndiannessIfHostBig(index);

		static const std::string dummy_semantics("__dummy__");
		const std::string* attr_semantics = &dummy_semantics;

		if (index == 0) // we don't handle secondary attributes for now
		{
			switch ((AttribSemantic) semantic)
			{
			case AS_POSITION:
				attr_semantics = &MeshIndexed::attr_semantics_position;
				fprintf(stdout, "AS_POSITION\n");
				break;
			case AS_BLEND_WEIGHTS:
				attr_semantics = &MeshIndexed::attr_semantics_blend_weights;
				fprintf(stdout, "AS_BLEND_WEIGHTS\n");
				break;
			case AS_NORMAL:
				attr_semantics = &MeshIndexed::attr_semantics_normal;
				fprintf(stdout, "AS_NORMAL\n");
				break;
			case AS_COLOR:
				fprintf(stdout, "AS_COLOR (discarded)\n");
				break;
			case AS_TEXTURE_COORD:
				attr_semantics = &MeshIndexed::attr_semantics_texcoord;
				fprintf(stdout, "AS_TEXTURE_COORD\n");
				break;
			case AS_BITANGENT:
				fprintf(stdout, "AS_BITANGENT (discarded)\n");
				break;
			case AS_TANGENT:
				fprintf(stdout, "AS_TANGENT (discarded)\n");
				break;
			}
		}

		assert(attr_semantics);

		AttrElementType attr_element_type = (AttrElementType) -1;

		switch ((AttribType) type)
		{
		case AT_FLOAT1:
		case AT_FLOAT2:
		case AT_FLOAT3:
		case AT_FLOAT4:
			attr_element_type = ATTR_ELEMENT_FLOAT32;
			break;
		case AT_UBYTE4:
		case AT_COLOR:
			attr_element_type = ATTR_ELEMENT_UINT8;
			break;
		}

		assert(attr_element_type != -1);

		unsigned attr_element_count = 0;

		switch ((AttribType) type)
		{
		case AT_FLOAT1:
			attr_element_count = 1;
			break;
		case AT_FLOAT2:
			attr_element_count = 2;
			break;
		case AT_FLOAT3:
			attr_element_count = 3;
			break;
		case AT_FLOAT4:
		case AT_UBYTE4:
		case AT_COLOR:
			attr_element_count = 4;
			break;
		}

		assert(attr_element_count);

		bind_attr[src_buffer].push_back(AttrDescriptor(*attr_semantics, attr_element_type, attr_element_count));
	}

	// mesh data
	uint32 num_vertices(0);
	uint16 num_buffers(0);
	source.read(reinterpret_cast< char* >(&num_vertices), sizeof(num_vertices));
	source.read(reinterpret_cast< char* >(&num_buffers), sizeof(num_buffers));

	swapEndiannessIfHostBig(num_vertices);
	swapEndiannessIfHostBig(num_buffers);

	assert(num_buffers <= max_num_buffers);

	MeshIndexed mesh;
	mesh.setRegister(reg_buf);
	mesh.setNumVertBuffers(num_buffers);

	for (unsigned i = 0; i < num_buffers; ++i)
	{ 
		uint16 bindIndex;
		uint16 vertexSize;
		source.read(reinterpret_cast< char* >(&bindIndex), sizeof(bindIndex));
		source.read(reinterpret_cast< char* >(&vertexSize), sizeof(vertexSize));

		swapEndiannessIfHostBig(bindIndex);
		swapEndiannessIfHostBig(vertexSize);

		if (vertexSize == 0)
			continue;

		char str[16];
		sprintf(str, "_%03d_vertex", bindIndex);

		const std::string vb_name(filename + str);
		AttrBuffer16* vb = reg_buf->insert(vb_name);

		vb->setBufferType(AttrBuffer16::BUFFER_TYPE_VERTEX);
		vb->setBufferLength(num_vertices);
		vb->setAttrScript(bind_attr[bindIndex].size(), &*bind_attr[bindIndex].begin());
		vb->contentUnflatten(source);

		vb->acquireLock();

		for (unsigned i = 0; i < vb->getBufferLength(); ++i)
			for (unsigned j = 0; j < vb->getAttrCount(); ++j)
			{
				const AttrDescriptor& d = vb->getAttrDescriptor(j);

				switch (d.element_type)
				{
				case ATTR_ELEMENT_FLOAT32:
					for (unsigned k = 0; k < d.element_count; ++k)
						swapEndiannessIfHostBig(*reinterpret_cast< float* >(vb->getRawBits() + i * vb->getBufferRecSize() + vb->getAttrOffset(j) + k * 4));
					break;
				}
			}

		vb->releaseLock();

		mesh.setVertexBuffer(vb_name, i);
	}

	uint16 index_format(0);
	uint32 num_indices(0);
	source.read(reinterpret_cast< char* >(&index_format), sizeof(index_format));
	source.read(reinterpret_cast< char* >(&num_indices), sizeof(num_indices));

	swapEndiannessIfHostBig(index_format);
	swapEndiannessIfHostBig(num_indices);

	if (num_indices != 0)
	{
		AttrElementType attr_element_type = (AttrElementType) -1;

		switch ((IndexType) index_format)
		{
		case INDEX_16BIT:
			attr_element_type = ATTR_ELEMENT_UINT16;
			break;
		case INDEX_32BIT:
			attr_element_type = ATTR_ELEMENT_UINT32;
			break;
		}

		assert(attr_element_type != -1);

		AttrDescriptor desc(MeshIndexed::attr_semantics_index, attr_element_type, 3);

		const std::string ib_name(filename + "_index");
		AttrBuffer16* ib = reg_buf->insert(ib_name);

		ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);
		ib->setBufferLength(num_indices / 3);
		ib->setAttrScript(1, &desc);
		ib->contentUnflatten(source);

		switch ((IndexType) index_format)
		{
		case INDEX_16BIT: {
			VectorArray< uint16, 3 > idx;

			ib->getBulkTransfer(idx);

			for (unsigned i = 0; i < idx.count(); ++i)
			{
				swapEndiannessIfHostBig(idx.get(i)[0]);
				swapEndiannessIfHostBig(idx.get(i)[1]);
				swapEndiannessIfHostBig(idx.get(i)[2]);
			}

			ib->setBulkTransfer(idx);
			} break;

		case INDEX_32BIT: {
			VectorArray< uint32, 3 > idx;

			ib->getBulkTransfer(idx);

			for (unsigned i = 0; i < idx.count(); ++i)
			{
				swapEndiannessIfHostBig(idx.get(i)[0]);
				swapEndiannessIfHostBig(idx.get(i)[1]);
				swapEndiannessIfHostBig(idx.get(i)[2]);
			}

			ib->setBulkTransfer(idx);
			} break;
		}

		mesh.setIndexBuffer(ib_name);
	}

	// bounding box
	vect3 bmin;
	vect3 bmax;
	source.read(reinterpret_cast<char*>(&bmin), sizeof(bmin));
	source.read(reinterpret_cast<char*>(&bmax), sizeof(bmax));

	swapEndiannessIfHostBig(bmin.dekast()[0]);
	swapEndiannessIfHostBig(bmin.dekast()[1]);
	swapEndiannessIfHostBig(bmin.dekast()[2]);

	swapEndiannessIfHostBig(bmax.dekast()[0]);
	swapEndiannessIfHostBig(bmax.dekast()[1]);
	swapEndiannessIfHostBig(bmax.dekast()[2]);

	mesh.setBBox(Box3(bmin, bmax));

	// bounding sphere (unused)
	vect3 center;
	float radius;
	source.read(reinterpret_cast<char*>(&center), sizeof(center));
	source.read(reinterpret_cast<char*>(&radius), sizeof(radius));

	source.close();

	meshes.push_back(mesh);

	return true;
}

} // namespace rend
